﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Threading;
using System.Security.Cryptography;
using System.Windows.Threading;
using System.Windows;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;
using System.Windows.Media;

namespace WpfImageDuplicates
{
    public class FileImage : INotifyPropertyChanged, IFileImage
    {
        public static LazyLoader<FileImage> _lazyLoader = new LazyLoader<FileImage>((f) => LoadImageData(f));

        public event EventHandler ThumbnailSet;
        public event EventHandler<ObjectEventArgs<string>> DataLoaded;

        protected string _hashValue;
        protected ImageSource _imageSource;
        protected Bitmap _bitmap;
        protected BitmapImage _bitmapImage;
        protected bool _lazyLoading;
        protected byte[] _imageBytes;
        protected object _lockObject = new object();
        protected string _fileName;
        protected bool _deleted;
        protected Bitmap _thumbnail;
        protected int _fileSize;

        public static void LoadImageData(FileImage fileImage)
        {
            fileImage.Load();
        }

        public virtual byte[] ImageBytesCached
        {
            get { return _imageBytes; }
        }

        public void BeginLazyLoad()
        {
            _lazyLoading = true;
            _lazyLoader.LazyLoad(this, (fi) => fi.DoneLazyLoad(), _lockObject);
        }

        public void Locked(Action a, Func<bool> test)
        {
            lock (_lockObject)
            {
                if (test())
                {
                    a();
                }
            }
        }

        public virtual byte[] ImageBytes
        {
            get
            {
                if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted)
                    return null;

                if (_imageBytes == null && !_lazyLoading)
                {
                    Locked(() => BeginLazyLoad(), () => _imageBytes == null && !_lazyLoading);
                }

                return _imageBytes;
            }
            set
            {
                if (_imageBytes != value)
                {
                    _imageBytes = value;
                    OnPropertyChanged("ImageBytes");
                }
            }
        }


        public string MD5HashCached
        {
            get { return _hashValue; }
        }

        public void FreeBitmapImage(bool releaseThumb)
        {
            if (_bitmapImage != null)
            {
                lock (_lockObject)
                {
                    if (_bitmapImage != null)
                    {
                        ReleaseFile(releaseThumb);
                    }
                }
            }
        }

        public void DoneLazyLoad()
        {
            Dispatcher dispatcher = Application.Current.Dispatcher;

            if (dispatcher != null)
            {
                dispatcher.BeginInvoke(new VoidDelegate(() =>
                {
                    this.InitAndSetImage();
                    this._lazyLoading = false;
                }));
            }
            else
            {
                this.InitAndSetImage();
                this._lazyLoading = false;
            }
        }

        public void FreeBitmap(bool releaseThumb)
        {
            if (_bitmap != null)
            {
                lock (_lockObject)
                {
                    if (_bitmap != null)
                    {
                        _bitmap.Dispose();
                        _bitmap = null;
                        ReleaseFile(releaseThumb);
                    }
                }
            }
        }

        protected bool _suspendUnload;

        public bool SuspendUnload
        {
            get
            {
                return _suspendUnload;
            }
            set
            {
                if (_suspendUnload != value)
                {
                    _suspendUnload = value;
                }
            }
        }

        public void ReleaseFile(bool releaseThumb)
        {
            _imageBytes = null;
            Bitmap = null;
            BitmapImage = null;

            if (releaseThumb)
            {
                Thumbnail = null;
            }
        }

        public string MD5Hash
        {
            get
            {
                if (string.IsNullOrEmpty(_hashValue) && !_deleted)
                {
                    lock (_lockObject)
                    {
                        if (string.IsNullOrEmpty(_hashValue) && !_deleted)
                        {
                            if (_imageBytes == null)
                            {
                                byte[] bytes = File.ReadAllBytes(_fileName);
                                MD5Hash = bytes.CalculateMD5Hash();
                            }
                            else
                            {
                                MD5Hash = _imageBytes.CalculateMD5Hash();
                            }
                        }
                    }
                }

                return _hashValue;
            }
            set
            {
                if (_hashValue != value)
                {
                    _hashValue = value;
                    OnPropertyChanged("MD5Hash");
                }
            }
        }

        public virtual bool BitmapLoaded
        {
            get { return _bitmap != null; }
        }

        public virtual bool BitmapImageLoaded
        {
            get { return _bitmapImage != null; }
        }

        public virtual BitmapImage BitmapImageCached
        {
            get
            {
                if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted || _lazyLoading)
                    return null;

                return _bitmapImage;
            }
        }

        void InitAndSetImage()
        {
            if (_deleted)
                return;

            if (_imageBytes == null)
                throw new Exception("No image bytes");

            BitmapImage = CreateBitmapImageFromBytes(_imageBytes);
            Bitmap = Bitmap.FromStream(new MemoryStream(_imageBytes)) as Bitmap;

            if (DataLoaded != null)
            {
                DataLoaded(this, new ObjectEventArgs<string>(string.Empty));
            }
        }

        public void Load()
        {
            if (_deleted || !File.Exists(_fileName))
                return;

            ImageBytes = File.ReadAllBytes(_fileName);

            FileSize = ImageBytes.Length;
        }

        public virtual ImageSource ImageSourceCached
        {
            get { return _imageSource; }
        }

        
        public virtual ImageSource ImageSource
        {
            get
            {
                if (_bitmap == null)
                    return null;

                if (_imageSource == null)
                {
                    Locked(() => Bitmap.CreateImageSourceFromBitmap(), () => _imageSource == null);
                }

                return _imageSource;
            }
            set
            {
                if (_imageSource != value)
                {
                    _imageSource = value;
                    OnPropertyChanged("ImageSource");
                }
            }
        }

        public BitmapImage CreateBitmapImageFromBytes(byte[] bytes)
        {
            MemoryStream ms = new MemoryStream(_imageBytes);

            BitmapImage temp = new BitmapImage();
            temp.BeginInit();
            temp.StreamSource = ms;
            temp.EndInit();

            return temp;
        }

        public virtual BitmapImage BitmapImage
        {
            get
            {
                if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted)
                    return null;

                if (_bitmapImage == null)
                {
                    Locked(() => BeginLazyLoad(), () => _bitmapImage == null && !_lazyLoading);
                }

                return _bitmapImage;
            }
            set
            {
                if (_bitmapImage != value)
                {
                    _bitmapImage = value;
                    OnPropertyChanged("BitmapImage");
                }
            }
        }

        public virtual void LoadBitmap()
        {
            if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted)
                return;

            if (_imageBytes == null && !_lazyLoading)
            {
                Locked(() => BeginLazyLoad(), () => _imageBytes == null && !_lazyLoading);                
            }
        }

        public virtual Bitmap BitmapCached
        {
            get
            {
                if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted || _lazyLoading)
                    return null;

                return _bitmap;
            }
        }

        public virtual Bitmap Bitmap
        {
            get
            {
                LoadBitmap();

                if (_bitmap == null && _imageBytes != null)
                {
                    Locked(() => Bitmap = Bitmap.FromStream(new MemoryStream(_imageBytes)) as Bitmap, () => _bitmap == null && _imageBytes != null);                    
                }
                return _bitmap;
            }
            set
            {
                if (_bitmap != value)
                {
                    _bitmap = value;
                    OnPropertyChanged("Bitmap");
                }
            }
        }

        public FileImage()
        {
        }

        void _imageInfo_DataLoaded(object sender, ObjectEventArgs<string> e)
        {
            DataLoaded -= _imageInfo_DataLoaded;
            OnDataLoaded(e.obj);
        }

        public void OnDataLoaded(string s)
        {
            if (DataLoaded != null)
                DataLoaded(this, new ObjectEventArgs<string>(s));
        }

        public void CopyTo(IFileImage other)
        {
            other.FileName = this._fileName;
            other.FileSize = this._fileSize;
            other.Bitmap = this.BitmapCached;
            other.ImageBytes = this.ImageBytesCached;
            other.ImageSource = this.ImageSourceCached;
            other.BitmapImage = this.BitmapImageCached;
            other.MD5Hash = this.MD5HashCached;
            other.Thumbnail = this._thumbnail;
        }

        public IFileImage MakeCopy()
        {
            FileImage newImage = new FileImage();
            this.CopyTo(newImage);
            return newImage;
        }

        public bool Deleted
        {
            get { return _deleted; }
        }

        public FileImage(string fileName)
            : this()
        {
            this._fileName = fileName;
        }

        public override bool Equals(object obj)
        {
            FileImage f = obj as FileImage;

            if (f != null)
            {
                return f.FileName == FileName;
            }

            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public string FileName
        {
            get
            {
                return _fileName;
            }
            set
            {
                if (_fileName != value)
                {
                    lock (_lockObject)
                    {
                        _fileName = value;
                        MarkDirty(true);
                    }
                    OnPropertyChanged("FileName");
                }
            }
        }

        public virtual void MarkDirty(bool clearThumb)
        {
            Bitmap = null;
            BitmapImage = null;
            ImageSource = null;
            FileSize = 0;

            if (clearThumb)
            {
                Thumbnail = null;
            }
        }

        public virtual Bitmap ThumbnailCached
        {
            get
            {
                return _thumbnail;
            }
            set
            {
                if (_thumbnail != value)
                {
                    _thumbnail = value;
                    OnPropertyChanged("Thumbnail");
                }
            }
        }

        public static FileImage CreateNew(string s)
        {
            var fi = new FileImage(s);
            fi.ThumbnailSet += new EventHandler(FileImageCollection.v_ThumbnailSet);
            return fi;
        }

        public virtual Bitmap Thumbnail
        {
            get
            {
                if (string.IsNullOrEmpty(_fileName) || !File.Exists(_fileName) || _deleted)
                    return null;

                if (_thumbnail == null)
                {
                    lock (_lockObject)
                    {
                        if (_thumbnail == null)
                        {
                            Image img = System.Drawing.Image.FromFile(_fileName);
                            int newX, newY;

                            Util2.GetScaledSize(img.Width, img.Height, (int)Settings.Values.ThumbWidth, (int)Settings.Values.ThumbHeight, out newX, out newY);                            
                            Bitmap bmOut = new Bitmap(newX, newY, img.PixelFormat);

                            using (Graphics g = Graphics.FromImage(bmOut))
                            {
                                g.CompositingQuality = CompositingQuality.HighQuality;
                                g.InterpolationMode = InterpolationMode.High;
                                g.SmoothingMode = SmoothingMode.HighQuality;
                                g.DrawImage(img, 0, 0, newX, newY);
                            }

                            Thumbnail = bmOut;
                        }
                    }
                }

                return _thumbnail;
            }
            set
            {
                if (_thumbnail != value)
                {
                    _thumbnail = value;
                    OnThumbnailSet();
                    OnPropertyChanged("Thumbnail");
                }
            }
        }

        public void OnThumbnailSet()
        {
            if (ThumbnailSet != null)
                ThumbnailSet(this, EventArgs.Empty);
        }

        public bool Delete()
        {
            _deleted = true;
            ReleaseFile(true);

            try
            {
                File.Delete(_fileName);
            }
            catch (Exception ex)
            {
                Console.Write(ex.ToString());
                return false;
            }

            return true;
        }

        public int FileSize
        {
            get
            {
                if (_fileSize == 0)
                {
                    lock (_lockObject)
                    {
                        if (_fileSize == 0)
                        {
                            FileInfo f = new FileInfo(FileName);
                            _fileSize = (int)f.Length;
                        }
                    }
                }

                return _fileSize;
            }
            set
            {
                if (_fileSize != value)
                {
                    _fileSize = value;
                    OnPropertyChanged("FileSize");
                }
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
